#coding:utf8
import RPi.GPIO as GPIO
import time
from threading import Thread
from multiprocessing import Process, Value
# import atexit  
# atexit.register(GPIO.cleanup)

class MyCar(object):
    def __init__(self):
        """
        分别定义左边有右边占空比，为方便调节两边的速度
        pwm_hz为频率
        """
        self.pwm_dc_left = 60
        self.pwm_dc_right = 60
        self.pwm_hz = 500

        # L298n模块pwm信号引脚
        self.pwm_pin = (16,18)

        # L298n模块控制信号引脚
        self.en_pin = (11,12,13,15)

        # 前进，左转，右转,停止，模式对应L298n模块使能引脚状态
        self.forward_status = (1,0,1,0)
        self.turn_left_status = (0,1,1,0)
        self.turn_right_status = (1,0,0,1)
        self.stop_status = (0,0,0,0)
        self.back_status = (0,1,0,1)


        # 声波距离感应器
        # distance_trigger_pin为控制引脚，distance_echo_pin为输入引脚
        # distance_cm为当前距离
        # distance_critical为检测到物体报警距离
        # distance_cal为是否计算距离，即是否发射超声波
        self.distance_trigger_pin = 31
        self.distance_echo_pin = 29
        self.distance_cm = -1
        self.distance_critical = 20
        self.distance_cal = False

        # 存储小车当前状态，初始化为0，前进为1,转向为2，停止为3，后退为4
        self.move_status = 0

        # 小车是否需要停止，该值为真是小车停止（单独线程轮询检测）
        self.stop_signal = Value('b',0)
        # self.stop_signal = False

        # 舵机pwm信号引脚
        self.servo_pin = 7
        # 舵机是否需要转动一圈，初始值为否
        self.servo_turn = Value('b',0)
        # 舵机转到不同角度下物品的距离，右边为负角度，左边为正角度
        # 距离为对应角度下的距离，单位厘米
        self.servo_angle = []
        self.servo_distance = []

        # 定义是否为漫步模式，该模式为前进，遇到障碍物转弯，然后再前进
        self.walk_model = True

        # 左右测速模块信号引脚，低电平为有障碍物通过
        self.velocity_left_pin = 32
        self.velocity_right_pin = 22
        # 定义左右轮的速度
        self.left_velocity = 0
        self.right_velocity = 0
        self.left_distance = 0
        self.right_distance = 0


        # 定义红外人体感应器引脚
        self.humman_pin = 36
        # 人体红外感应器是否感应到人体
        self.humman_activity = False

        """调用初始化函数"""
        self.setup()


    def setup(self):
        """设置小车pwm和控制引脚初始化"""
        # 定义引脚号规则，使用BOARD模式
        GPIO.setmode(GPIO.BOARD)
        GPIO.setwarnings(False)
        # 控制引脚初始化，全部为0，默认初始状态为停止
        for pin in self.en_pin:
            GPIO.setup(pin, GPIO.OUT)
            GPIO.output(pin, GPIO.LOW)
        # pwm实例，存放在列表中
        self.pwm_signals = []
        for pin in self.pwm_pin:
            GPIO.setup(pin, GPIO.OUT)
            self.pwm_signals.append(GPIO.PWM(pin, self.pwm_hz))

        # 以 self.pwm_dc_forward 占空比开启pwm信号，默认初始占空比为转弯模式占空比
        self.pwm_signals[0].start(0)
        self.pwm_signals[1].start(0)

        # 设置声波距离感应器
        GPIO.setup(self.distance_trigger_pin,GPIO.OUT)
        GPIO.setup(self.distance_echo_pin,GPIO.IN)
        # 设置舵机pwm引脚
        GPIO.setup(self.servo_pin,GPIO.OUT,initial=False)
        self.senvor_pwm = GPIO.PWM(self.servo_pin,50)
        self.senvor_pwm.start(0)
        time.sleep(2)
        # 设置测速模块引脚为输入引脚
        GPIO.setup(self.velocity_left_pin,GPIO.IN)
        GPIO.setup(self.velocity_right_pin,GPIO.IN)


        # 定义红外人体感应器引脚为输入引脚
        GPIO.setup(self.humman_pin,GPIO.IN)


    def forward(self):
        """向前进，通过控制占空比慢慢加速"""
        self.move_status = 1
        # 调节占空比为设定值
        self.pwm_signals[0].ChangeDutyCycle(self.pwm_dc_left)
        self.pwm_signals[1].ChangeDutyCycle(self.pwm_dc_right)
        # 使能端赋值为前进模式
        GPIO.output(self.en_pin,self.forward_status)
        

    def dealStopSignal(self):
        """线程，处理小车停止信号的线程"""
        while True:
            if self.stop_signal.value == 1:
                # 使能端赋值为停止模式
                GPIO.output(self.en_pin,self.stop_status)
                self.move_status = 3
            # time.sleep(0.01)



    def backup(self):
        """后退"""
        self.move_status = 4
        GPIO.output(self.en_pin,self.back_status)

    def listenStopSignal(self):
        """调用线程监听小车收否需要停止信号stop_signal"""
        print('listenStopSignal start...')
        deal2 = Process(target=self.dealStopSignal)
        deal2.daemon = True
        deal2.start()
        # deal2.join()
    def shutDown(self):
        '''因为--del--回收机制不好用，采用自定义程序处理收尾工作'''
        for pwm_signal in self.pwm_signals:
            pwm_signal.stop()
        GPIO.cleanup()

    def getDistance(self):
        '''线程，根据超声波模块计算距离'''
        while True:
            if self.distance_cal :
                # 发送一个触发信号，开始发送超声波测距
                GPIO.output(self.distance_trigger_pin,GPIO.HIGH)
                time.sleep(0.0001)
                GPIO.output(self.distance_trigger_pin,GPIO.LOW)
                # 获得开始时间，电平为1
                count = 10000
                while GPIO.input(self.distance_echo_pin) != True and count>0:
                    count = count-1
                start = time.time()
                # 获得结束时间，电平为0
                count = 10000
                while GPIO.input(self.distance_echo_pin) != False and count>0:
                    count = count-1
                finish = time.time()
                # 根据时间计算距离
                pulse_len = finish-start
                self.distance_cm = pulse_len/0.000058
            time.sleep(0.001)

    def listenDistance(self):
        '''调用线程监听超声波距离感应器的数值'''
        print('listenDistance start...')
        deal3 = Thread(target=self.getDistance)
        deal3.setDaemon(True)
        deal3.start()

    def servoTurn(self):
        '''线程，当变量servo_turn为真时，转动电机一圈，并且将servo_turn的值设为假'''
        # turn around is [0,-90],[-90,0],[0,90],[90,0]
        pwm_all=(7.5,7,6.5,6,5.5,5,4.5,4,3.5,3,3,3.5,4,4.5,5,5.5,6,6.5,7,7.5,8,8.5,9,9.5,10,10.5,11,11.5,12,12,11.5,11,10.5,10,9.5,9,8.5,8,7.5)
        while True:
            if self.servo_turn.value == 1 :
                self.servo_turn.value = 0
                # 初始化距离和角度列表数据
                self.servo_angle=[]
                self.servo_distance=[]
                self.senvor_pwm.ChangeDutyCycle(0)
                time.sleep(0.2)
                
                for num in range(len(pwm_all)):
                    self.senvor_pwm.ChangeDutyCycle(pwm_all[num])
                    time.sleep(0.02)
                    self.senvor_pwm.ChangeDutyCycle(0)
                    time.sleep(0.12)
                    # 第10-28个元素为从-90度到90度的范围
                    # 在这个范围读取超声波距离，并将角度和距离分别写入变量
                    if num >= 10 and num <= 28:
                        angle = (pwm_all[num]-7.5)*18
                        self.servo_angle.append(angle)
                        self.servo_distance.append(self.distance_cm)
                self.senvor_pwm.ChangeDutyCycle(0)


    def listenServoTurn(self):
        '''调用线程监听servo_turn变量是否为真，为真则转动舵机'''
        print('listenServoTurn start...')
        deal4 = Thread(target=self.servoTurn)
        deal4.setDaemon(True)
        deal4.start()

        # deal4 = Process(target=self.servoTurn)
        # deal4.daemon = True
        # deal4.start()



    def turn(self,angle):
        """转弯，参数为角度，右转为正，左转为负，旋转后小车停止"""
        self.move_status = 2
        # 调节占空比为设定值
        self.pwm_signals[0].ChangeDutyCycle(self.pwm_dc_left-10)
        self.pwm_signals[1].ChangeDutyCycle(self.pwm_dc_right-10)
        # 开始转动
        if angle > 0:
            # 右转
            # print('turn right')
            GPIO.output(self.en_pin,self.turn_right_status)
        else :
            # 左转
            # print('turn left')
            GPIO.output(self.en_pin,self.turn_left_status)


    def dealHuman(self):
        '''thread deal human activity sensor
        the sensor output pin from 0 to 1 when detect activiry
        and will keep 1 about 2.5 seconds,after the time, the pin will output 0
        '''
        GPIO.add_event_detect(self.humman_pin, GPIO.RISING)
        while True:
            if GPIO.event_detected(self.humman_pin):
                self.humman_activity = True
                # 3 seconds to pass the sensor keep output 1 period
                time.sleep(3)
            else:
                self.humman_activity = False

    def listHuman(self):
        '''listen velocity change, use single thread'''
        print('listenhuman start...')
        deal6 = Process(target=self.dealHuman)
        deal6.daemon = True
        deal6.start()


    def callbackVelocityLeft(self,num):
        '''callback function '''
        # a new thread will create to count the left and right distance
        # the number of sensor is 20, the circumference is 21.5cm
        # at anytime, a rising singnal will add 21.5/20=1.075cm to distance
        self.left_distance += 1.06


    def callbackVelocityRight(self,num):
        '''callback function '''
        # a new thread will create to count the left and right distance
        # the number of sensor is 20, the circumference is 21.5cm
        # at anytime, a rising singnal will add 21.5/20=1.075cm to distance
        self.right_distance += 1.06

    def dealVelocity(self):
        '''get velocity by count senosr number'''
        # add event to velocity sensor pin
        GPIO.add_event_detect(self.velocity_left_pin ,  GPIO.RISING ,callback=self.callbackVelocityLeft)
        GPIO.add_event_detect(self.velocity_right_pin , GPIO.RISING ,callback=self.callbackVelocityRight)
        # count the number of rising in the count_time period
        count_time = 0.2
        while True:
            # get distance before conunt
            distance_left_start = self.left_distance
            distance_right_start = self.right_distance
            time.sleep(count_time)
            # by call callbackVelocity function, the distance is increased in the sleep time
            # distance divid time, get velocity
            self.left_velocity = (self.left_distance-distance_left_start)/count_time
            self.right_velocity = (self.right_distance-distance_right_start)/count_time
            print('velocity left =',self.left_velocity,'velocity right =',self.right_velocity)
            print('diatance left =',self.left_distance,'distance right =',self.right_distance)

    def listenVelocity(self):
        '''listen velocity change, use single thread'''
        print('listenVelocity start...')
        deal5 = Thread(target=self.dealVelocity)
        deal5.setDaemon(True)
        deal5.start()






if __name__ == '__main__':
    car1 = MyCar()
    car1.listenStopSignal()
    # car1.forward()
    # time.sleep(1)

    '''
    # 监听超声波距离感应器，得到距离
    car1.listenDistance()
    car1.distance_cal = True

    # 监听舵机感应器
    car1.listenServoTurn()
    car1.servo_turn.value=1
    time.sleep(5)
    '''
    # 监听人体感应
    car1.listHuman()
    while True:
        print(car1.humman_activity)
        time.sleep(1)
    # 监听速度
    # car1.listenVelocity()


    # car1.turn(-10)
    # time.sleep(1)
    car1.stop_signal.value = 1
    # car1.shutDown()
    print("all done")


